fix(genesis): normalize genesis identity comparisons - #374
Conversation
|
Reviewed this end-to-end, including running the tests. The premise is real, the fix is correct, and the tests are genuine regression coverage. Two things need attention before merge: the fix is incomplete in one file, and CI will not execute any of it. Verified the premise
export const schemaAddress = z.string().regex(/^0x[0-9a-fA-F]{40}$/) as z.Schema<Address>So mixed casing is accepted and two spellings of one identity are representable. Confirmed. Verified the tests actually catch the bugI ran them both ways rather than taking the diff at its word:
They're real regression tests, not tautological. Good. Incomplete:
|
| file | unique addresses | valid EIP-55 |
|---|---|---|
assets/devnet/config.json |
26 | 26 |
assets/mainnet/config.json |
40 | 40 |
assets/testnet/config.json |
26 | 26 |
All 92 already pass, so enforcing it changes no committed config and no output. It also catches single-character typos, which normalization silently accepts.
I'd keep this PR as the safe, reviewable fix and treat checksum enforcement as a separate change — but it's worth recording as the durable version, because normalization-at-comparison relies on every future author remembering.
Minor
security/is a new top-level directory that doesn't exist onmain. Worth confirming maintainers want that location rather thandocs/.- The security note ends with "Before upstream submission, run the repository's normal TypeScript formatting, linting, and unit-test commands" — that's a working note to yourself, and reads oddly in a committed file since this is the upstream submission.
overrides: Record<string, unknown>in the test helper drops type-checking on the override, so a typo'd key would silently produce a config that passes for the wrong reason. Typing it asPartial<ReturnType<typeof configWithValidators>>keeps the tests honest.- 9 commits including staging/unstaging churn (
stage EIP-7702 research,remove unrelated finding,separate EIP-7702 research). Worth squashing so the history is just the fix, tests, and doc.
For what it's worth, AccountCreator.ts is clean — its Map/Set keys are numeric registration IDs, not hex, so it isn't affected.
Disclosure: I'm an external community contributor, not affiliated with Circle, with no write access to this repository. Advisory only. Test results above were produced locally against PR head c7189f3 and main at de76122; the EIP-55 check used a self-tested keccak256 implementation verified against the standard empty-string and abc vectors.
20d4dc8 to
4b3bd40
Compare
4b3bd40 to
dea806c
Compare
|
Validation update |
Thanks for the detailed review. I addressed the NativeFiatToken minter finding as suggested. |
|
Re-verified end-to-end at ConfirmedRan the PR's test files against
So the minter normalization is pinned by a test that genuinely fails without it. Counts reproduce: 14 focused, 47 full Hardhat suite, both exit 0. The proxy-address test is vacuous
const proxyAddress = '0x3600000000000000000000000000000000000002'
proxyAddress.toUpperCase().replace('0X', '0x') // -> identical stringThat address contains no alphabetic hex digits (only Corollary: that production line is also a no-op — and so is the "inconsistency" around itThe same fact makes the normalization itself unreachable: data.proxy.address.toLowerCase() !== DEFAULT_VALIDATOR_REGISTRY_PROXY_ADDRESS.toLowerCase()No mixed-case spelling of an all-numeric address exists, so this can never differ from the raw comparison. Harmless to keep as defensive, but it is not fixing a reachable case. The trap: two comparisons of identical shape were left un-normalized, and they look like the fix being incomplete —
They are not defects. All four system-contract constants in The distinction that matters: the checks worth normalizing compare two config-supplied values (minter vs minter, operator vs proxy admin, controller vs controller) — both sides free-form, both able to vary in case. Comparisons against a hardcoded system constant have one fixed side, and here that side has no letters. Your six real fixes are all in the first category; only the proxy-address one is in the second. Two small consequences:
Everything else stands — the fix is materially more complete than the previous round, and the minter case was the one with a real silent-overwrite consequence. Disclosure: I'm an external community contributor, not affiliated with Circle, with no write access to this repository. Advisory only. Results above are local, against PR head |
|
Thanks for the thorough independent review and for verifying the regression behavior against both the PR and main. I’ve addressed the identified gaps: Added case-insensitive minter uniqueness validation and regression coverage. Updated the security documentation to reflect the actual affected validation paths. |
Summary
Normalize hexadecimal identities at comparison boundaries during genesis validation.
schemaAddressandschemaHexaccept mixed-case hexadecimal values, but several genesis validation checks previously compared raw strings. Because hexadecimal casing does not change the represented bytes, equivalent identities with different casing could bypass uniqueness or role-separation checks.What changed
Regression coverage
Added regression tests covering:
The ValidatorRegistry proxy-address casing test was intentionally removed because the current hard-coded system-contract address contains only numeric hexadecimal digits, so an alternate mixed-case representation is not possible.
Security impact
This is a genesis/configuration integrity issue rather than a standalone permissionless exploit.
Without normalization, a malformed or conflicting genesis configuration could represent the same underlying address/key bytes multiple times while bypassing string-based uniqueness or role-separation checks. This could result in unintended duplicate identities or conflicting role assignments during genesis construction.
The minter case is particularly important because duplicate addresses with different casing could pass validation while later being represented by the same underlying address during genesis processing.
The fix makes comparisons operate on the represented hexadecimal identity rather than its textual casing, while preserving the original input representation for serialization and diagnostics.
Validation
Regression tests are included in:
tests/unit/native-fiat-token-genesis-validation.test.tstests/unit/validator-manager-genesis-validation.test.tsThe full Arc toolchain was not executed in this environment. Before merging, run the repository's normal validation commands, including:
make test-unit-hardhatmake lintand verify the resulting CI checks.
Note that the repository CI configuration does not currently execute the new Hardhat regression tests as part of the standard
make test-unitpath, so the dedicated Hardhat test command should be verified explicitly.Scope
This PR is intentionally limited to genesis identity normalization and its regression coverage.
The ValidatorRegistry proxy-address comparison is not an affected casing path because the current hard-coded system-contract address contains no alphabetic hexadecimal digits.
EIP-7702 transaction-pool research was kept separate from this PR.